﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using MathExpression;

namespace SimplexSolver
{
    internal class TargetParametrizedSimplexSolver
    {
        public event EventHandler<MessageTransferNeededEventArgs> MessageTransferNeeded;

        public ISolution Solutions { get; private set; }

        private double[] _delta;

        private ILPTask _task;

        private List<Segment> _segments;

        public void SetTask(ILPTask task)
        {
            _task = task;
        }

        public void SetDelta(double[] delta)
        {
            _delta = (double[]) delta.Clone();
        }

        public void Solve()
        {
            //todo refactoring for dividing on many methods
            if (!CheckValues())
            {
                return;
            }
            if (!CheckAllowable())
            {
                OnMessageTransferNeeded("Задача не является допустимой",
                                        MessageTransferNeededEventArgs.EMessageType.Error);
                return;
            }
            _segments = new List<Segment>();
            var solutionItems = new List<SolutionItem>();
            ILPTask paramTask = CreateTask();
            SimplexTable table = SimplexTableBuilder.Create(paramTask);
            TargetFunction.EDirection direction = paramTask.Target.Direction;
            bool hasNegativeInfinitySegment = false;
            bool hasPositiveInfinitySegment = false;
            while (!hasPositiveInfinitySegment || !hasNegativeInfinitySegment)
            {
                List<Segment> segments = GetSegments(table.TargetRow, direction);

                Debug.WriteLine(string.Format("\r\nSegment List"));
                foreach (Segment segment in segments)
                {
                    Debug.WriteLine(string.Format("{0}\t{1}", segment.Start, segment.End));
                }

                Segment unionSegment = GetUnionSegment(segments);
                int columnIndex;
                if (unionSegment != null)
                {
                    IMathExpression[] vector = table.GetSolutionVector(_task.Size);
                    IMathExpression value = paramTask.Target.GetValueOf(vector);
                    var singleSolution = new SingleSolution(value, vector);
                    double start = unionSegment.Start;
                    double end = unionSegment.End;
                    _segments.Add(new Segment(start, end));
                    solutionItems.Add(new SolutionItem(start, end, singleSolution));
                    if (Math.Abs(start - double.MinValue) < double.Epsilon)
                    {
                        hasNegativeInfinitySegment = true;
                    }
                    if (hasNegativeInfinitySegment)
                    {
                        columnIndex = GetMaxIndexOfSegments(segments, unionSegment);
                    }
                    else
                    {
                        columnIndex = GetMinIndexOfSegments(segments, unionSegment);
                    }
                    if (Math.Abs(end - double.MaxValue) < double.Epsilon)
                    {
                        hasPositiveInfinitySegment = true;
                    }
                }
                else
                {
                    columnIndex = GetIndexOfWeakSegment(segments);
                    if (columnIndex < 0)
                    {
                        IMathExpression[] targetRow = table.TargetRow;
                        columnIndex = GetMinElementIndex(targetRow);
                    }
                }
                int rowIndex = GetDirectRowIndexForCalculating(table, columnIndex);
                bool hasSolution = columnIndex >= 0 && rowIndex >= 0;
                if (!hasSolution)
                {
                    break;
                }
                table.Recalculate(rowIndex + 1, columnIndex + 1);
            }
            Solutions = new ParameterSolution(solutionItems.ToArray());
        }

        private int GetIndexOfWeakSegment(List<Segment> segments)
        {
            for (int i = 0; i < segments.Count; i++)
            {
                if (_segments.Any(segment => segment.Contains(segments[i])))
                {
                    return i;
                }
            }
            return -1;
        }

        private int GetMinIndexOfSegments(List<Segment> segments, Segment unionSegment)
        {
            int columnIndex = -1;
            for (int i = 0; i < segments.Count; i++)
            {
                if (Math.Abs(segments[i].Start - unionSegment.Start) < double.Epsilon)
                {
                    columnIndex = i;
                    break;
                }
            }
            return columnIndex;
        }

        private int GetMaxIndexOfSegments(List<Segment> segments, Segment unionSegment)
        {
            int columnIndex = -1;
            for (int i = 0; i < segments.Count; i++)
            {
                if (Math.Abs(segments[i].End - unionSegment.End) < double.Epsilon)
                {
                    columnIndex = i;
                    break;
                }
            }
            return columnIndex;
        }

        private ILPTask CreateTask()
        {
            TargetFunction oldTarget = _task.Target;
            var paramTarget = new IMathExpression[_task.Size];
            for (int i = 0; i < _task.Size; i++)
            {
                double freeValue = (oldTarget.Coefs[i].Data as ScalarData).Value;
                double paramValue = _delta[i]*Math.Sign(freeValue);
                paramTarget[i] = ExpressionFactory.Create(freeValue, paramValue);
            }
            var task = new LPTask(_task.Size);
            foreach (Restriction restriction in _task.Restrictions)
            {
                task.AddRestriction(restriction);
            }
            task.SetTargetFunction(new TargetFunction(paramTarget, _task.Target.Direction));
            return task;
        }

        private bool CheckAllowable()
        {
            ReadOnlyCollection<Restriction> restrictions = _task.Restrictions;
            foreach (Restriction restriction in restrictions)
            {
                double value = (restriction.Limit.Data as ScalarData).Value;
                if (value < 0.0)
                {
                    return false;
                }
            }
            return true;
        }

        private bool CheckValues()
        {
            if (_task == null)
            {
                throw new NullReferenceException("Не задана задача для решения");
            }
            if (_delta == null)
            {
                throw new NullReferenceException("Не заданы смещения для целевой функции");
            }
            if (_task.Size != _delta.Length)
            {
                throw new Exception("Число элементов смещения для целевой функции не соответствует размеру задачи");
            }
            bool isCorrect = true;
            ReadOnlyCollection<Restriction> restrictions = _task.Restrictions;
            foreach (Restriction restriction in restrictions)
            {
                if (!(restriction.Limit.Data is ScalarData))
                {
                    isCorrect = false;
                    break;
                }
                if (restriction.DataRow.Any(item => !(item.Data is ScalarData)))
                {
                    isCorrect = false;
                    break;
                }
            }
            if (!isCorrect)
            {
                OnMessageTransferNeeded("В ограничениях задачи элементы неверного типа. Требуемый тип: Scalar",
                                        MessageTransferNeededEventArgs.EMessageType.Warning);
            }
            return isCorrect;
        }

        private Segment GetUnionSegment(IEnumerable<Segment> segments)
        {
            double startValue = double.MinValue;
            double endValue = double.MaxValue;
            foreach (Segment segment in segments)
            {
                if (segment.Start > startValue)
                {
                    startValue = segment.Start;
                }
                if (segment.End < endValue)
                {
                    endValue = segment.End;
                }
            }
            if (startValue < endValue)
            {
                return new Segment(startValue, endValue);
            }
            return null;
        }

        private List<Segment> GetSegments(IEnumerable<IMathExpression> expressions, TargetFunction.EDirection direction)
        {
            var segments = new List<Segment>();
            foreach (IMathExpression expression in expressions)
            {
                Segment curSegment = GetSegment(direction, expression);
                if (curSegment != null)
                {
                    segments.Add(curSegment);
                }
            }
            return segments;
        }

        private static Segment GetSegment(TargetFunction.EDirection direction, IMathExpression expression)
        {
            var data = expression.Data as PolynomialData;
            double freeValue = (data.Values[0].Data as ScalarData).Value;
            double paramValue = (data.Values[1].Data as ScalarData).Value;
            if (Math.Abs(paramValue - 0.0) < double.Epsilon)
            {
                return null;
            }
            paramValue *= -1.0;
            double value = freeValue/paramValue;
            Segment segment;
            if (direction == TargetFunction.EDirection.Max)
            {
                if (paramValue < 0.0)
                {
                    segment = new Segment(value, double.MaxValue);
                }
                else
                {
                    segment = new Segment(double.MinValue, value);
                }
            }
            else
            {
                if (paramValue < 0.0)
                {
                    segment = new Segment(double.MinValue, value);
                }
                else
                {
                    segment = new Segment(value, double.MaxValue);
                }
            }
            return segment;
        }

        private int GetDirectRowIndexForCalculating(SimplexTable table, int columnIndex)
        {
            int index = -1;
            int count = table.RowsCount - 1;
            double min = double.MaxValue;
            for (int i = 0; i < count; i++)
            {
                var scalarData = table.RestrictionMatrix[i][columnIndex].Data as ScalarData;
                if (scalarData != null)
                {
                    double value = scalarData.Value;
                    if (value > 0.0)
                    {
                        var data = table.FreeValueColumn[i].Data as ScalarData;
                        if (data != null)
                        {
                            double freeValue = data.Value;
                            value = freeValue/value;
                        }
                        if (value < min)
                        {
                            min = value;
                            index = i;
                        }
                    }
                }
            }
            return index;
        }

        private int GetMinElementIndex(IMathExpression[] values)
        {
            double min = double.MaxValue;
            int index = -1;
            int count = values.Length;
            for (int i = 0; i < count; i++)
            {
                double value = double.MaxValue;
                IExpressionData data = values[i].Data;
                if (data is ScalarData)
                {
                    value = (data as ScalarData).Value;
                }
                else if (data is PolynomialData)
                {
                    var scalar = (data as PolynomialData).Values[0].Data as ScalarData;
                    if (scalar != null)
                    {
                        value = scalar.Value;
                    }
                }
                if (value < min)
                {
                    min = value;
                    index = i;
                }
            }
            return index;
        }

        private void OnMessageTransferNeeded(string message, MessageTransferNeededEventArgs.EMessageType type)
        {
            if (MessageTransferNeeded != null)
            {
                var e = new MessageTransferNeededEventArgs(message, type);
                MessageTransferNeeded(this, e);
            }
        }

        private class Segment
        {
            public double Start { get; private set; }
            public double End { get; private set; }

            public Segment(double start, double end)
            {
                Start = start;
                End = end;
            }

            public bool Contains(Segment segment)
            {
                return segment.Start >= Start && segment.End <= End;
            }
        }
    }
}